/* Declarations for integer types.

   This file is part of khipu.

   khipu is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#ifndef __KP_INTEGER__
#define __KP_INTEGER__   1

#include "arith.hpp"
#include "interp.hpp"

KP_DECLS_BEGIN

#ifdef KP_ARCH_WIDE

inline constexpr int as_int (object obj)
{
  return ((int)(obj & 0xffffffffu));
}

inline constexpr object fixint (int val)
{
  return (ptrtype ((object)(uint32_t)val, typecode::INT));
}

inline constexpr bool fixint_p (object obj)
{
  return (itype (obj) == typecode::INT);
}

// We use the full lower word to store the int.
const int FIXINT_BITS = 32;

#else

inline constexpr int as_int (object obj)
{
  return ((int)((obj >> 3) |
    ((-(obj >> 31)) & ((1u << 31) | (1u << 30) | (1u << 29)))));
}

inline constexpr object fixint (int val)
{
  return ((uint32_t)val << 3);
}

inline constexpr bool fixint_p (object obj)
{
  return ((obj & 3) == 0);
}

// The 3 lower bits are reserved.
const int FIXINT_BITS = 29;

#endif

const intptr_t FIXINT_MAX = ((intptr_t)1 << (FIXINT_BITS - 1)) - 1;
const intptr_t FIXINT_MIN = -((intptr_t)1 << (FIXINT_BITS - 1));

// Test if an integer can be represented as an immediate value.
template <typename T>
inline bool fitsfixint_p (T val)
{
  return (val >= FIXINT_MIN && val <= FIXINT_MAX);
}

struct bigint : public varobj
{
  static const int code = typecode::BIGINT;

  int len;
  limb_t *data;

  static bigint* alloc_raw (int len);

  static object make (interpreter *interp, int64_t qval);
  static object make (interpreter *interp, uint64_t qval);

  static bool cast (object self, int64_t& out);
  static bool cast (object self, uint64_t& out);

#ifndef KP_ARCH_WIDE
  static object make (interpreter *interp, intptr_t lval)
    {
      return (bigint::make (interp, (int64_t)lval));
    }

  static object make (interpreter *interp, uintptr_t lval)
    {
      return (bigint::make (interp, (uint64_t)lval));
    }
#endif

  void local_init (limb_t *data = nullptr, int len = 0)
    {
      this->len = len;
      this->data = data;
    }
};

inline bigint* as_bigint (object obj)
{
  return ((bigint *)unmask (obj));
}

#ifdef KP_ARCH_WIDE

inline constexpr bool bigint_p (object obj)
{
  return (itype (obj) == typecode::BIGINT);
}

#else

inline bool bigint_p (object obj)
{
  return (varobj_p (obj) && as_varobj(obj)->vo_type == typecode::BIGINT);
}

#endif

// Allocate a bigint with LEN limbs.
KP_EXPORT object alloc_bigint (interpreter *interp, int len);

template <typename T>
object intobj (interpreter *interp, T val)
{
  if (kp_likely (fitsfixint_p (val)))
    interp->retval = fixint (val);
  else if (val < 0)
    bigint::make (interp, (int64_t)val);
  else
    bigint::make (interp, (uint64_t)val);

  return (interp->retval);
}

// Arithmetic operations with integers.
KP_EXPORT object add_iI (interpreter *interp, object x, object y);
KP_EXPORT object add_II (interpreter *interp, object x, object y);

KP_EXPORT object sub_iI (interpreter *interp, object x, object y);
KP_EXPORT object sub_II (interpreter *interp, object x, object y);

KP_EXPORT object mul_iI (interpreter *interp, object x, object y);
KP_EXPORT object mul_II (interpreter *interp, object x, object y);

KP_EXPORT result<object> div_ii (interpreter *interp, object x, object y);
KP_EXPORT object div_iI (interpreter *interp, object x, object y);
KP_EXPORT result<object> div_Ii (interpreter *interp, object x, object y);
KP_EXPORT object div_II (interpreter *interp, object x, object y);

KP_EXPORT result<object> idiv_ii (interpreter *interp, object x, object y);
KP_EXPORT object idiv_iI (interpreter *interp, object x, object y);
KP_EXPORT result<object> idiv_Ii (interpreter *interp, object x, object y);
KP_EXPORT object idiv_II (interpreter *interp, object x, object y);

KP_EXPORT result<object> mod_ii (interpreter *interp, object x, object y);
KP_EXPORT object mod_iI (interpreter *interp, object x, object y);
KP_EXPORT result<object> mod_Ii (interpreter *interp, object x, object y);
KP_EXPORT object mod_II (interpreter *interp, object x, object y);

// Logical operations with integers.
KP_EXPORT object land_iI (interpreter *interp, object x, object y);
KP_EXPORT object land_II (interpreter *interp, object x, object y);

KP_EXPORT object lor_iI (interpreter *interp, object x, object y);
KP_EXPORT object lor_II (interpreter *interp, object x, object y);

KP_EXPORT object xor_iI (interpreter *interp, object x, object y);
KP_EXPORT object xor_II (interpreter *interp, object x, object y);

KP_EXPORT object lsh_ii (interpreter *interp, object x, object y);
KP_EXPORT object lsh_Ii (interpreter *interp, object x, object y);

KP_EXPORT object rsh_ii (interpreter *interp, object x, object y);
KP_EXPORT object rsh_Ii (interpreter *interp, object x, object y);

// Comparisons between integers.
KP_EXPORT int cmp_iI (interpreter *interp, object x, object y);
KP_EXPORT int cmp_II (interpreter *interp, object x, object y);
KP_EXPORT bool eq_II (interpreter *interp, object x, object y);

// Additional unary functions.
KP_EXPORT object neg_I (interpreter *interp, object val);
KP_EXPORT object not_I (interpreter *interp, object val);
KP_EXPORT uint32_t hash_I (interpreter *interp, object val);

// I/O with integers.

KP_EXPORT result<int64_t> write_i (interpreter *interp,
                                   stream *strm, object obj, io_info& info);

KP_EXPORT result<int64_t> write_I (interpreter *interp,
                                   stream *strm, object obj, io_info& info);

KP_EXPORT result<int64_t> pack_i (interpreter *interp,
                                  stream *strm, object obj, pack_info& info);

KP_EXPORT result<int64_t> pack_I (interpreter *interp,
                                  stream *strm, object obj, pack_info& info);

KP_EXPORT result<object> unpack_i (interpreter *interp,
                                   stream *strm, pack_info& info, bool save);

KP_EXPORT result<object> unpack_I (interpreter *interp,
                                   stream *strm, pack_info& info, bool save);

// Methods that are worthy of inlining.

inline object add_ii (interpreter *interp, object x, object y)
{
  intptr_t r = (intptr_t)as_int (x) + as_int (y);
  return (intobj (interp, r));
}

inline object sub_ii (interpreter *interp, object x, object y)
{
  intptr_t r = (intptr_t)as_int (x) - as_int (y);
  return (intobj (interp, r));
}

inline object neg_i (interpreter *interp, object x)
{
  intptr_t r = -(intptr_t)as_int (x);
  return (intobj (interp, r));
}

inline object not_i (interpreter *interp, object x)
{
#ifdef KP_ARCH_WIDE
  kp_return (x ^ 0xffffffffu);
#else
  kp_return (x ^ (0xffffffffu & ~7));
#endif
}

inline object mul_ii (interpreter *interp, object x, object y)
{
  int64_t q = (int64_t)as_int (x) * as_int (y);
  return (intobj (interp, q));
}

inline object land_ii (interpreter *interp, object x, object y)
{
  kp_return (x & y);
}

inline object lor_ii (interpreter *interp, object x, object y)
{
  kp_return (x | y);
}

inline object xor_ii (interpreter *interp, object x, object y)
{
  kp_return (x ^ y);
}

inline int cmp_ii (interpreter *, object x, object y)
{
  int v1 = as_int (x), v2 = as_int (y);
  return (v1 < v2 ? -1 : v1 > v2);
}

inline object hash_i (interpreter *, object x)
{
  return ((uint32_t)as_int (x));
}

KP_DECLS_END

#endif
